{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parte 2: Introdução ao Federated Learning\n",
    "\n",
    "Na última seção, nós aprendemos sobre PointerTensors, que criam a infraestrutura necessária que precisamos para proteção de privacidade em Deep Learning. Nessa seção, nós iremos ver como usar essas ferramentas básicas para implementar nosso primeiro algoritmo de proteção de privacidade em Deep Learning: Federated Learning. \n",
    "\n",
    "Autores:\n",
    "- Andrew Trask - Twitter: [@iamtrask](https://twitter.com/iamtrask)\n",
    "\n",
    "Tradução:\n",
    "- Jeferson Silva - Twitter: [@jefersonnpn](https://twitter.com/jefersonnpn)\n",
    "\n",
    "### O que é Federated Learning?\n",
    "\n",
    "\n",
    "É uma simples, porém poderosa maneira de treinar modelos de Deep Learning. Pense em dados de treinamento, são sempre o resultado de algum processo de coleta. As pessoas (via dispositivos) geram dados ao gravar eventos no mundo real. Normalmente, esses dados são agregados em uma simples localização central em que você pode treinar um modelo de aprendizegam de máquina. O Federated Learning vira isso de ponta a cabeça!\n",
    "\n",
    "Ao invés de trazer dados de treinamento para o modelo (um servidor central), você trás o modelo para os dados de treinamento (onde quer que ele esteja).\n",
    "\n",
    "A ideia é que isso permita que quem estiver criando os dados possua a única cópia permanente e, assim, mantenha o controle sobre quem já teve acesso a eles. Muito legal, não acha?"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Seção 2.1 - Um exemplo brinquedo de Federated Learning\n",
    "\n",
    "Vamos começar treinando um modelo de exemplo da maneira centralizada. Isso é tão simples quanto obter o modelo. Primeiro precisamos:\n",
    "\n",
    "- um conjunto de dados para brincarmos\n",
    "- um modelo\n",
    "- alguma lógica básica de treinamento para treinar um modelo que se ajusta aos dados\n",
    "\n",
    "Obs.: Se para você, a seguinte API ainda não é familar. Visite o [fast.ai](http://fast.ai) e faça os seus cursos antes de continuar esse tutorial."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import torch\n",
    "from torch import nn\n",
    "from torch import optim"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "\n",
    "# Dataset de exemplo\n",
    "data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)\n",
    "target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)\n",
    "\n",
    "# Modelo simples de exemplo\n",
    "model = nn.Linear(2,1)\n",
    "\n",
    "def train():\n",
    "    # Lógica de treinamento\n",
    "    opt = optim.SGD(params=model.parameters(),lr=0.1)\n",
    "    for iter in range(20):\n",
    "\n",
    "        # 1) apague os gradientes anteriores (caso eles existam)\n",
    "        opt.zero_grad()\n",
    "\n",
    "        # 2) faça uma predição\n",
    "        pred = model(data)\n",
    "\n",
    "        # 3) calcule o erro, isto é, o quanto perdemos\n",
    "        loss = ((pred - target)**2).sum()\n",
    "\n",
    "        # 4) descubra quais pesos nos causaram essa perda\n",
    "        loss.backward()\n",
    "\n",
    "        # 5) altere esses pesos\n",
    "        opt.step()\n",
    "\n",
    "        # 6) mostre o progresso até então\n",
    "        print(loss.data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "E aí está! Nós treinamos um modelo simples da maneira convencional. Todos os nossos dados estão agregados em nossa máquina local e podemos usá-los para fazer atualizações em nosso modelo. O aprendizado federado, no entanto, não funciona dessa maneira. Então, vamos modificar este exemplo para fazê-lo da maneira federada!\n",
    "\n",
    "Então, o que precisamos:\n",
    "\n",
    "- criar alguns *workers*, isto é, nós de computação\n",
    "- obter apontadores para dados de treinamento de cada *worker*\n",
    "- atualizadar a lógica de treinamento para fazer o aprendizado federado\n",
    "\n",
    "    Novas etapas de treinamento:\n",
    "    - enviar modelo para um *worker* e fazer correções/atualizações\n",
    "    - treinar com os dados que lá estão localizados\n",
    "    - pegar o modelo de volta e repetir o processo com o próximo *worker* "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import syft as sy\n",
    "hook = sy.TorchHook(torch)\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# cria dois nós (ou duas entidades de computação)\n",
    "\n",
    "bob = sy.VirtualWorker(hook, id=\"bob\")\n",
    "alice = sy.VirtualWorker(hook, id=\"alice\")\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Nosso dataset de exemplo\n",
    "data = torch.tensor([[0,0],[0,1],[1,0],[1,1.]], requires_grad=True)\n",
    "target = torch.tensor([[0],[0],[1],[1.]], requires_grad=True)\n",
    "\n",
    "# obtenha apontadores para os dados de treinamento de cada worker \n",
    "# enviando alguns dados de treinamento para bob e alice\n",
    "data_bob = data[0:2]\n",
    "target_bob = target[0:2]\n",
    "\n",
    "data_alice = data[2:]\n",
    "target_alice = target[2:]\n",
    "\n",
    "# Inicialize o modelo\n",
    "model = nn.Linear(2,1)\n",
    "\n",
    "data_bob = data_bob.send(bob)\n",
    "data_alice = data_alice.send(alice)\n",
    "target_bob = target_bob.send(bob)\n",
    "target_alice = target_alice.send(alice)\n",
    "\n",
    "# organize os apontadores numa lista\n",
    "datasets = [(data_bob,target_bob),(data_alice,target_alice)]\n",
    "\n",
    "opt = optim.SGD(params=model.parameters(),lr=0.1)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def train():\n",
    "    # Lógica de treinamento\n",
    "    opt = optim.SGD(params=model.parameters(),lr=0.1)\n",
    "    for iter in range(10):\n",
    "        \n",
    "        # NOVO) itere através do dataset de cada worker\n",
    "        for data,target in datasets:\n",
    "            \n",
    "            # NOVO) envie o modelo para corrigir/atualizar o worker\n",
    "            model.send(data.location)\n",
    "\n",
    "            # 1) apague os gradientes anteriores (caso eles existam)\n",
    "            opt.zero_grad()\n",
    "\n",
    "            # 2) faça uma predição\n",
    "            pred = model(data)\n",
    "\n",
    "            # 3) calcule o erro, isto é, o quanto perdemos\n",
    "            loss = ((pred - target)**2).sum()\n",
    "\n",
    "            # 4) descubra quais pesos nos causaram essa perda\n",
    "            loss.backward()\n",
    "\n",
    "            # 5) atualize os pesos\n",
    "            opt.step()\n",
    "            \n",
    "            # NOVO) pegue o modelo (com gradientes)\n",
    "            model.get()\n",
    "\n",
    "            # 6) mostre o progresso até então\n",
    "            print(loss.get()) # NOVO) leve edição... precisa usar o .get() a partir do loss\\\n",
    "    \n",
    "# média federada"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "train()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Pronto!\n",
    "\n",
    "E voilà! Agora estamos treinando um modelo muito simples de Deep Learning usando o Federated Learning! Enviamos o modelo para cada *worker*, geramos um novo gradiente e, em seguida, devolvemos o gradiente ao servidor local, onde atualizamos nosso modelo global. Nunca, neste processo, jamais vemos ou solicitamos acesso aos dados subjacentes ao treinamento! Preservamos a privacidade de Bob e Alice!!!\n",
    "\n",
    "## Problemas deste exemplo\n",
    "\n",
    "Embora este exemplo seja uma boa introdução ao Federated Learning, ele ainda apresenta algumas falhas importantes. A mais notável, é que quando chamamos `model.get()` e recebemos o modelo atualizado de Bob ou Alice, podemos aprender muito sobre os dados de treinamento de ambos apenas observando seus gradientes. Em alguns casos, podemos restaurar os dados de treinamento de forma completa!\n",
    "\n",
    "Então o que poderíamos fazer? Bem, a primeira estratégia empregada pelas pessoas é **calcular a média do gradiente em vários indivíduos antes de carregá-lo no servidor central**. Essa estratégia, no entanto, exigirá um uso mais sofisticado dos objetos _PointerTensor_. Portanto, na próxima seção, vamos passar um algum tempo aprendendo sobre funcionalidades mais avançada ao utilizar ponteiros e, em seguida, iremos atualizar este exemplo de Federated Learning.\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Parabéns!!! - Hora de se juntar a comunidade!\n",
    "\n",
    "Parabéns por concluir esta etapa do tutorial! Se você gostou e gostaria de se juntar ao movimento em direção à proteção de privacidade, propriedade descentralizada e geração, demanda em cadeia, de dados em IA, você pode fazê-lo das seguintes maneiras!\n",
    "\n",
    "### Dê-nos uma estrela em nosso repo do PySyft no GitHub\n",
    "\n",
    "A maneira mais fácil de ajudar nossa comunidade é adicionando uma estrela nos nossos repositórios! Isso ajuda a aumentar a conscientização sobre essas ferramentas legais que estamos construindo.\n",
    "\n",
    "- [Star PySyft](https://github.com/OpenMined/PySyft)\n",
    "\n",
    "### Junte-se ao Slack!\n",
    "\n",
    "A melhor maneira de manter-se atualizado sobre os últimos avanços é se juntar à nossa comunidade! Você pode fazer isso preenchendo o formulário em [http://slack.openmined.org](http://slack.openmined.org)\n",
    "\n",
    "### Contribua com o projeto!\n",
    "\n",
    "A melhor maneira de contribuir para a nossa comunidade é se tornando um contribuidor do código! A qualquer momento, você pode acessar a página de *Issues* (problemas) do PySyft no GitHub e filtrar por \"Projetos\". Isso mostrará todas as etiquetas (tags) na parte superior, com uma visão geral de quais projetos você pode participar! Se você não deseja ingressar em um projeto, mas gostaria de codificar um pouco, também pode procurar mais mini-projetos \"independentes\" pesquisando problemas no GitHub marcados como \"good first issue\".\n",
    "\n",
    "- [Projetos do PySyft](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3AProject)\n",
    "- [Etiquetados como Good First Issue](https://github.com/OpenMined/PySyft/issues?q=is%3Aopen+is%3Aissue+label%3A%22good+first+issue%22)\n",
    "\n",
    "### Doar\n",
    "\n",
    "Se você não tem tempo para contribuir com nossa base de códigos, mas ainda deseja nos apoiar, também pode se tornar um Apoiador em nosso Open Collective. Todas as doações vão para hospedagem na web e outras despesas da comunidade, como hackathons e meetups!\n",
    "\n",
    "[Página do Open Collective do OpenMined](https://opencollective.com/openmined)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
